#include "Map.h"
Map::Map()
{
    nombreDeSalle=10;



     //On initiallise le personnage avant de créer le niveau car on place le joueur sur le départ dans la fonction randomMapGenerator
    for(int i=0;i<NY;i++){
        for(int j=0;j<NX;j++){
            niveau_[i][j]=Info[VIDE];

        }
    }
    randomMapGenerator();



}




//Dessine le caractère à la posiion (x,y) à l'écran
const void Map::drawPos(int x,int y)
{

      Affichage::affiche(niveau_[y][x].display);
}
//Met à jour la position des personnages sur la carte dans le tableau niveauPerso_

const bool Map::estAccessible(int x,int y)
{
    if( x>=0 && y>=0 && x<NX &&y<NY)
        return niveau_[y][x].accessible;
    else
        return false;
}
const bool Map::estAccessible(position a,int dir)
{
    a.changePos(dir);
    if( a.x>=0 && a.y>=0 && a.x<NX &&a.y<NY)
        return niveau_[a.y][a.x].accessible;
    else
        return false;
}

//Ecrite la salle dans niveau à partir d'une structure salle
void Map::writeSalle(salle centre)
{

    for(int y=centre.y-1;y<NY;y++){
        for(int x=centre.x-1;x<NX;x++){
            if(x<centre.x+centre.tailleX && centre.x<=x && y<centre.y+centre.tailleY && centre.y<=y){
                niveau_[y][x]=Info[POINT];
            }
        }
    }
}
void Map::addMur()
{


    for(int y=0;y<NY;y++){
        for(int x=0;x<NX;x++){
            if(niveau_[y][x].interieur){
                if(niveau_[y][x-1].vide)
                    niveau_[y][x-1]=Info[MURDROIT];
                if (niveau_[y][x+1].vide)
                    niveau_[y][x+1]=Info[MURDROIT];
                if (niveau_[y+1][x].vide)
                    niveau_[y+1][x]=Info[MURBAS];
                if (niveau_[y-1][x].vide)
                    niveau_[y-1][x]=Info[MURHAUT];
                }

            }

        }

}
void Map::bricolage()
{
    string s(TextManager::to_string(time(NULL))+".csv");
    ofstream f(s.c_str());
    vector<position> a;
    for(int y=0;y<NY;y++){
        for(int x=0;x<NX;x++){
            f<<niveau_[y][x].display;
        }
        f<<endl;
    }
        f.close();
}
void Map::getCostMap(int  (&costmap)[NY][NX])
{
    for(int y=0;y<NY;y++){
        for(int x=0;x<NX;x++){
            if(niveau_[y][x].accessible)
                costmap[y][x]=1;
            else
                costmap[y][x]=100; //Distance maximale sur la map donc équivalent à inaccesible
            }
    }

}
void Map::getCostMapCouloir(int  (&costmap)[NY][NX])
{
    for(int y=0;y<NY;y++){
        for(int x=0;x<NX;x++){
            if(!niveau_[y][x].interieur && niveau_[y][x].accessible)
                costmap[y][x]=1;
            else if(!(niveau_[y][x].accessible) && !(niveau_[y][x].vide) )
                costmap[y][x]=3;
            else
                costmap[y][x]=2;
            }
    }

}
//Test si la salle n'est pas en collision avec les autres salles (vector)
const bool Map::testAjoutSalle(salle aTester)
{

    for(int y=aTester.y;y<(aTester.y+aTester.tailleY);y++){

        for(int x=aTester.x;x<(aTester.x+aTester.tailleX);x++){

            if(!niveau_[y][x].vide)         //If pour eviter que les salles ne se superposent
                return false;
            else if(!niveau_[y][x+1].vide)  //Les if suivants sont destinés à eviter les murs mitoyens
                return false;
            else if(!niveau_[y][x-1].vide)
                return false;
            else if(!niveau_[y-1][x].vide)
                return false;
            else if(!niveau_[y+1][x].vide)

                return false;


        }
    }
    return true;
}
//Renvoie un vector contennant le chemin pour aller de salle debut à salle fin (l'algorithme n'est pas le a*)
const vector<position> Map::a_star(salle debut,salle fin)
{
    vector<position> chemin;
    position actuel;
    actuel.x=debut.x+debut.tailleX/2;
    actuel.y=debut.y+debut.tailleY/2;
    int xfin=fin.x+fin.tailleX/2;
    int yfin=fin.y+fin.tailleY/2;

    /*
    //On veut crée un chemin très "carré" donc on parcourt d'abord les x puis les y
    while(actuel.x<=fin.x || actuel.x>=(fin.x +fin.tailleX)){

        actuel.x=actuel.x+(xfin-actuel.x)/abs(xfin-actuel.x);

        chemin.push_back(actuel);

    }
    while(actuel.y<=fin.y || actuel.y>=(fin.y+fin.tailleY)){

            actuel.y=actuel.y+(yfin-actuel.y)/abs(yfin-actuel.y);
            chemin.push_back(actuel);

        }

    */
    int costmap[NY][NX];
    getCostMapCouloir(costmap);
    AStar a(actuel,position(xfin,yfin),costmap);
    a.a_star();

    return a.chemin();
}
//Crée un niveau aléatoire
void Map::randomMapGenerator()
{

       int i=1;

       salle ajout;
       while(i<=nombreDeSalle){//On génère la salle
            ajout=salleRandom();
            //on vérifie que la salle reste dans les limites du niveau et qu'elle n'ecrase pas une autre salle
            if((ajout.x+ajout.tailleX+1)<NX && (ajout.y+ajout.tailleY+1)<NY && testAjoutSalle(ajout)){

                writeSalle(ajout);
                if(salles_.size()!=0) // Si une salle existe déjà on veut créer un couloir entre les deux
                    writeChemin(a_star(salleLaPlusProche(ajout),ajout)); //On cherche la salle la plus proche, on cherche le chemin puis on le marque dans niveau
                salles_.push_back(ajout);   //On rajoute la salle au vector des salles déjà crées
                i++;

            }
       }
       creerSpawn();  //on rajoute un spawn pour le joueur
       creerFin();
       creerPortes();
       addMur();
}
//Génère un entier aléatoire en a et b

// Créer un point d'entrée pour le personnage de manière aléatoire
void Map::creerSpawn()
{
    spawn_=randomPositionInterieur();
    niveau_[spawn_.y][spawn_.x]=Info[SPAWN];  //On déplace le joueur au point d'entrée
}

//on ecrit le chemin sur la carte de niveau
void Map::writeChemin(vector<position> chemin)
{
    vector<position>::iterator itr;
    for(itr=chemin.begin();itr!=chemin.end();++itr){
        if(!niveau_[itr->y][itr->x].accessible )      //Si le case du chemin n'est pas déjà un endroit praticable
            niveau_[itr->y][itr->x]=Info[COULOIR];
    }
}

//Retourne la salle la plus proche en distance de manhattan par rapport à la salle ajout
Map::salle Map::salleLaPlusProche(salle ajout)
{
    vector<salle>::iterator itr;
    salle salleProche;
    int x=0;
    int y=0;
    int dst_min=150;
    int dst=0;
    int ajoutx=ajout.x+ajout.tailleX/2;
    int ajouty=ajout.y+ajout.tailleY/2;

    for(itr=salles_.begin();itr!=salles_.end();++itr){
         x=itr->x+itr->tailleX/2;
         y=itr->y+itr->tailleY/2;
         dst=abs(x-ajoutx)+abs(y-ajouty);
         if(  dst<dst_min){
             dst_min=dst;
             salleProche=*itr;
         }

    }
    return salleProche;

}


// Génère un salle aléatoirement
const Map::salle Map::salleRandom()
{
    salle s;
    //On génère le point haut-gauche
    s.x=randab(1,NX-1);
    s.y=randab(1,NY-1);
    //Puis la taille de la salle
    s.tailleX=randab(4,8);
    s.tailleY=randab(4,8);

    return s;
}
//Génère le point pour quitter le niveau: on génère trois point accessible puis on choisi le plus éloigné
void Map::creerFin()
{
    position random;
    random=randomPositionInterieur();
    position pos_max;
    int maximum=0;
    int dist=0;
    for(int i=0;i<3;i++){
        random=randomPositionInterieur();
        dist=abs(spawn_.x-random.x)+abs(spawn_.y-random.y); //On calculele distance de manhattan par rapport au spawn puis on choisit le maximum
        if(dist>maximum){
            maximum=dist;
            pos_max=random;

        }

    }
    fin_=pos_max;
    niveau_[pos_max.y][pos_max.x]=Info[FIN];
}
const position Map::getSpawn()
{
    return spawn_;
}
const position Map::getFin()
{
    return fin_;
}



const bool Map::isFin(position a){
    if(fin_ ==a)
        return true;
    else
        return false;

}
const bool Map::isSpawn(position a )
{
    if(a == spawn_)
        return true;
    else
        return false;
}
const position Map::randomPositionInterieur()
{
    int a = randab(0,salles_.size()-1);
    salle s=salles_[a];
    int x=randab(s.x,s.x+s.tailleX);
    int y=randab(s.y,s.y+s.tailleY);
    return position(x,y);
}
const bool Map::isInterieur(position a)
{
    return isInterieur(a.x,a.y);

}
const bool Map::getTransparence(position p)
{
    return getTransparence(p.x,p.y);

}
const bool Map::isInterieur(int x,int y)
{
    return niveau_[y][x].interieur;

}
const bool Map::getTransparence(int x,int y)
{
    return niveau_[y][x].transparent;

}
const bool Map::checkLOS(position a,position b)
{
    int dx =abs(b.x-a.x);
    int dy= abs(b.y-a.y);
    int sx=1;
    int sy=1;
    int err=dx-dy;
    int e2=0;
    if(a.x>b.x)
        sx=-1;
    if(a.y>b.y)
        sy=-1;
    while(a.x<NX && a.y<NY){
        if(a==b )
            return true;
        if(!getTransparence(a))
            return false;

         e2=2*err;
         if(e2>-dy){
            err-=dy;
            a.x+=sx;
        }
        if(a==b )
            return true;
        if(!getTransparence(a))
            return false;

        if(e2<dx){
            err+=dx;
            a.y+=sy;
        }

    }
    return true;

}

void Map::creerPortes()
{
    position random,pos_max,autre;
    random=randomPositionInterieur();
    int h =randab(0,4);
    switch(h){
        case 0:
            porte1_=position(random.x,0);
            porte2_=position(0,random.y);
        break;
        case 1:
            porte1_=position(NX-1,random.y);
            porte2_=position(random.x,NY-1);
        break;
        case 2:
            porte1_=position(NX-1,random.y);
            porte2_=position(random.x,0);
        break;
        case 3:
            porte1_=position(0,random.y);
            porte2_=position(random.x,NY-1);
        break;
    }
    /* for(int i=0;i<2;i++){
        random=randomPositionInterieur();
        dist=abs(spawn_.x-random.x)+abs(spawn_.y-random.y); //On calcule distance de manhattan par rapport au spawn puis on choisit le maximum
        if(dist>maximum && random!=spawn_ && random!=fin_){
            maximum=dist;
            pos_max=random;

        }
    }
    porte1_=pos_max; */
    niveau_[porte1_.y][porte1_.x]=Info[PORTE];
    //pos_max=randomPositionInterieur();
    //porte2_=pos_max;
    niveau_[porte2_.y][porte2_.x]=Info[PORTE];
    salle p1,p2;
    p1.x=porte1_.x;
    p1.y=porte1_.y;
    p1.tailleX=0;
    p1.tailleY=0;
    p2.x=porte2_.x;
    p2.y=porte2_.y;
    p2.tailleX=0;
    p2.tailleY=0;
    writeChemin(a_star(salleLaPlusProche(p1),p1));
    writeChemin(a_star(salleLaPlusProche(p2),p2));
}
bool Map::isChgt(position a)
{
    return (a==spawn_ || a==fin_|| a==porte1_ || a==porte2_ );

}
Type_Edge Map::chgtType(position a)
{
    if(a==spawn_)
        return ESC_MON;
    else if(a==fin_)
        return ESC_DES;
    else if(a==porte1_)
        return PORTE1;
    else if(a==porte2_)
        return PORTE2;
}
position Map::getPorte(int a)
{
    if(a==1)
        return porte1_;
    else
        return porte2_;
}
